package com.redisdatastorage.config;

import com.redisdatastorage.enums.RedisMode;
import com.redisdatastorage.utils.RedisObjectSerializer;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.*;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

/**
 * RedisConfig
 *
 * @author chenjing
 */
@Configuration
@EnableCaching
@EnableConfigurationProperties({RedisProperties.class})
public class RedisConfig {

    Logger logger = LoggerFactory.getLogger(RedisConfig.class);

    @Autowired
    private RedisProperties redisProperties;

    @Bean
    public LettuceClientConfiguration lettuceClientConfiguration() {
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
        builder.commandTimeout(Duration.ofMillis(redisProperties.getTimeout()));
        // 是否启用连接池
        if (redisProperties.getPool() != null && redisProperties.getPool().isEnable()) {
            GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
            genericObjectPoolConfig.setMaxIdle(redisProperties.getPool().getMaxIdle());
            genericObjectPoolConfig.setMinIdle(redisProperties.getPool().getMinIdle());
            genericObjectPoolConfig.setMaxTotal(redisProperties.getPool().getMaxActive());
            genericObjectPoolConfig.setMaxWaitMillis(redisProperties.getPool().getMaxWait());
            builder.poolConfig(genericObjectPoolConfig);
        }
        return builder.build();
    }

    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
        if (StringUtils.isEmpty(redisProperties.getPassword())) {
            logger.info("安全起见，建议给redis设置一个密码");
        }
        // 单机
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(standaloneConfiguration(), clientConfiguration);
        // 集群
        if (RedisMode.CLUSTER.getMode().equals(redisProperties.getMode())) {
            lettuceConnectionFactory = new LettuceConnectionFactory(clusterConfiguration(), clientConfiguration);
        }
        // 哨兵
        else if (RedisMode.CLUSTER.getMode().equals(redisProperties.getMode())) {
            lettuceConnectionFactory = new LettuceConnectionFactory(sentinelConfiguration(), clientConfiguration);
        }
        return lettuceConnectionFactory;
    }


    @Bean
    public RedisTemplate redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        // 自定义序列化value
        redisTemplate.setValueSerializer(new RedisObjectSerializer());
        // jackson 序列化
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // 初始化完成序列化的方法
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RedisConnection redisConnection(RedisTemplate<String, String> redisTemplate) {
        return redisTemplate.getConnectionFactory().getConnection();
    }

    @Bean
    public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }

    @Bean
    public HyperLogLogOperations<String, Object> hyperLogLogOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHyperLogLog();
    }

    @Bean
    public GeoOperations<String, Object> geoOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForGeo();
    }

    private List<RedisNode> getRedisNode(List<String> nodes) {
        List<RedisNode> list = new ArrayList<>();
        for (String node : nodes) {
            String ip = node.split(":")[0];
            Integer port = Integer.valueOf(node.split(":")[1]);
            RedisNode redisNode = new RedisNode(ip, port);
            list.add(redisNode);
        }
        return list;
    }

    /**
     * 单机
     */
    private RedisStandaloneConfiguration standaloneConfiguration() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setDatabase(redisProperties.getDatabase());
        configuration.setHostName(redisProperties.getHost());
        configuration.setPort(redisProperties.getPort());
        configuration.setPassword(redisProperties.getPassword());
        return configuration;
    }

    /**
     * 集群
     */
    private RedisClusterConfiguration clusterConfiguration() {
        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
        Assert.notEmpty(redisProperties.getCluster().getNodes(), "redis启用集群模式,服务节点nodes不能为空");
        clusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
        clusterConfiguration.setClusterNodes(getRedisNode(redisProperties.getCluster().getNodes()));
        clusterConfiguration.setPassword(redisProperties.getPassword());
        return clusterConfiguration;
    }

    /**
     * 哨兵
     */
    private RedisSentinelConfiguration sentinelConfiguration() {
        RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration();
        Assert.notEmpty(redisProperties.getSentinel().getNodes(), "redis启用哨兵模式, 服务节点nodes不能为空");
        sentinelConfiguration.setSentinels(getRedisNode(redisProperties.getSentinel().getNodes()));
        sentinelConfiguration.setMaster(redisProperties.getSentinel().getMaster());
        sentinelConfiguration.setDatabase(redisProperties.getDatabase());
        sentinelConfiguration.setPassword(redisProperties.getPassword());
        return sentinelConfiguration;
    }
}
